#include "myserialwidget.h"
#include "ui_myserialwidget.h"

#include "mycommandgenerator.h"
#include "myserialport.h"

#include <QSettings>
#include <QSerialPortInfo>
#include <QSerialPort>
#include <QListView>
#include <QDebug>
#include <QMessageBox>
#include <QTimer>
#include <QTime>

#define CONF_PORTNAME "/port/name"
#define CONF_BAUDRATE "/port/baudrate"
#define CONF_DEV_VID "/dev/vid"
#define CONF_DEV_PID "/dev/pid"

#define MYDebug() do{qDebug() << QTime::currentTime() << __FILE__ << "{" << __func__ << ":" << __LINE__ << "}";}while(0)



MySerialWidget::MySerialWidget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::MySerialWidget),
    serial(nullptr),
    mSerialMode(SERIAL_MODE_ASYNCHRONOUS)
{
    ui->setupUi(this);

    QString filename("./qtserial.ini");
    conf = new QSettings(filename, QSettings::IniFormat);

    setupUI();

    listPortTimer = new QTimer(this);
    closePortTimer = new QTimer(this);
    connect(listPortTimer, &QTimer::timeout, this, &MySerialWidget::slot_timer_listPort);
    connect(closePortTimer, &QTimer::timeout, this, &MySerialWidget::slot_timer_closePort);
}

MySerialWidget::~MySerialWidget()
{
    delete ui;
    delete conf;

    closePort();

    closePortTimer->stop();
    listPortTimer->stop();
    disconnect(listPortTimer, &QTimer::timeout, this, &MySerialWidget::slot_timer_listPort);
    disconnect(closePortTimer, &QTimer::timeout, this, &MySerialWidget::slot_timer_closePort);
    delete listPortTimer;
    delete closePortTimer;
}

void MySerialWidget::setupUI()
{
    MYDebug();
    // enable or disable
    ui->btnClosePort->setVisible(false);
    ui->btnOpenPort->setVisible(true);

    // style for combobox
    QString comboboxStylesheet = ("QComboBox { min-height: 30px; min-width: 60px;} QComboBox QAbstractItemView::item { min-height: 30px; min-width: 60px;}");
    ui->comboPorts->setStyleSheet(comboboxStylesheet);
    ui->comboBaudrate->setStyleSheet(comboboxStylesheet);

    QListView *listView = new QListView(ui->comboPorts);
    ui->comboPorts->setView(listView);

    QListView *listView2 = new QListView(ui->comboBaudrate);
    ui->comboBaudrate->setView(listView2);

    // baudrate in combobox
    QStringList baudrates;
    baudrates << "2400"<<"4800"<<"9600"<<"19200"<<"38400"<<"57600"<<"115200" << "460800"<<"921600"<< "1000000"<<"1152000"<< "2000000";
    ui->comboBaudrate->clear();
    ui->comboBaudrate->addItems(baudrates);
    ui->comboBaudrate->setEditable(true);

    QString baudrate = conf->value(CONF_BAUDRATE, "115200").toString();
    ui->comboBaudrate->setCurrentText(baudrate); // default bautrate

    // ports in combobox
    on_btnRefreshPorts_clicked();

    QTimer::singleShot(1000, this, [=]() {
        autoConnect();
    });
}

QList<QSerialPortInfo> MySerialWidget::availablePorts()
{
    quint16 vid = conf->value(CONF_DEV_VID, "0").toUInt();
    quint16 pid = conf->value(CONF_DEV_PID, "0").toUInt();

    QList<QSerialPortInfo> list;
    foreach (const QSerialPortInfo &info, QSerialPortInfo::availablePorts()) {
        if (vid > 0 && info.vendorIdentifier() != vid)
            continue;
        if (pid > 0 && info.productIdentifier() != pid)
            continue;

        QSerialPort serial1;
        serial1.setPort(info);
        if (serial1.open(QIODevice::ReadWrite)) {
            serial1.close();
            list.append(info);
        }
    }
    return list;
}


void MySerialWidget::on_btnRefreshPorts_clicked() {
    if (!ui->comboPorts->isEnabled())
        return;

    MYDebug();

    ui->comboPorts->clear();
    foreach (const QSerialPortInfo &info, availablePorts()) {
        QString text = QString("%1 - %2").arg(info.portName()).arg(info.description());
        ui->comboPorts->addItem(text);
    }
}

void MySerialWidget::on_btnOpenPort_clicked()
{
    MYDebug();
    if (serial) return;

    bool ok;
    qint32 baudrate = ui->comboBaudrate->currentText().toInt(&ok);
    if (!ok) {
        QMessageBox::information(this, "info", "Invalid Baudrate.");
        return;
    }
    QString portname = ui->comboPorts->currentText();
    if (portname.isEmpty()) {
        QMessageBox::information(this, "info", "Invalid port.");
        return;
    }
    QString msg;
    portname = portname.split(" - ")[0];
    ok = openPort(portname, baudrate, msg);

    if (ok) {
        updateUI(ok);
        closePortTimer->stop();
        closePortTimer->start(2000);
        listPortTimer->stop();
    } else {
        QMessageBox::information(this, "info", msg);
    }
}

void MySerialWidget::on_btnClosePort_clicked()
{
    MYDebug();

    closePort();

    updateUI(false);

    conf->setValue(CONF_PORTNAME, "");
    conf->sync();

    listPortTimer->stop();
    listPortTimer->start(2000);
    closePortTimer->stop();
}


bool MySerialWidget::openPort(QString portname, int baudrate, QString &error)
{
    MYDebug();

    bool ok = false;

    closePort();
    serial = MySerialPort::open(this, portname, baudrate, error);
    if (serial) {
        ok = true;

        bindEvents();

        conf->setValue(CONF_PORTNAME, portname);
        conf->setValue(CONF_BAUDRATE, baudrate);
        conf->sync();
    }
    return ok;
}

void MySerialWidget::autoConnect()
{
    MYDebug();

    bool found = false;
    QString portname = conf->value(CONF_PORTNAME, "").toString();
    if (!portname.isEmpty()) {
        for (int i=0; i<ui->comboPorts->count();i++) {
            QString text =  ui->comboPorts->itemText(i);
            if (text.contains(QString("%1 - ").arg(portname))) {
                ui->comboPorts->setCurrentIndex(i);
                found = true;
                break;
            }
        }
    }
    if (found) {
        on_btnOpenPort_clicked(); // reopen the last port
    } else {
        listPortTimer->start(2000);
    }
}

void MySerialWidget::closePort()
{
    MYDebug();

    unbindEvents();

    MySerialPort::close(serial);
    serial = nullptr;

}

void MySerialWidget::setMode(int new_mode)
{
    int pre_mode = mSerialMode;

    if (pre_mode == SERIAL_MODE_ASYNCHRONOUS && new_mode==SERIAL_MODE_SYNCHRONOUS) {
        unbindEvents();
    }

    mSerialMode = new_mode;

    if (pre_mode == SERIAL_MODE_SYNCHRONOUS && new_mode==SERIAL_MODE_ASYNCHRONOUS) {
        bindRecvEvent();
    }
}

void MySerialWidget::updateUI(bool isConnected)
{
    MYDebug();

    ui->comboPorts->setEnabled(!isConnected);
    ui->comboBaudrate->setEnabled(!isConnected);
    ui->btnRefreshPorts->setEnabled(!isConnected);
    ui->btnOpenPort->setVisible(!isConnected);
    ui->btnClosePort->setVisible(isConnected);

    // custom ui update
    // ui->pushButton->setEnabled(isConnected);
    emit sign_stateChanged(isConnected);
}

void MySerialWidget::bindRecvEvent()
{
    if (!serial) return;
    if (mSerialMode == SERIAL_MODE_SYNCHRONOUS) return;

    MYDebug();

    QObject::connect(serial, &QSerialPort::readyRead, this, &MySerialWidget::handleReadyRead);
}

void MySerialWidget::unbindRecvEvent()
{
    if (!serial) return;
    if (mSerialMode == SERIAL_MODE_SYNCHRONOUS) return;

    MYDebug();

    QObject::disconnect(serial, &QSerialPort::readyRead, this, &MySerialWidget::handleReadyRead);
}

void MySerialWidget::bindEvents()
{
    if (!serial) return;

    MYDebug();

    bindRecvEvent();

    QObject::connect(serial, &QSerialPort::bytesWritten, this, &MySerialWidget::handleBytesWritten);
    QObject::connect(serial, &QSerialPort::errorOccurred, this, &MySerialWidget::handleErrorOccurred);
}

void MySerialWidget::unbindEvents()
{
    if (!serial) return;

    MYDebug();

    unbindRecvEvent();

    QObject::disconnect(serial, &QSerialPort::bytesWritten, this, &MySerialWidget::handleBytesWritten);
    QObject::disconnect(serial, &QSerialPort::errorOccurred, this, &MySerialWidget::handleErrorOccurred);

}


void MySerialWidget::syncSendAndRecv(QByteArray &writtenData, QByteArray &recvData, int &recvLen, QWidget *widget)
{
    if (!serial || !serial->isOpen()) return;

    MYDebug();

    if (widget) widget->setEnabled(false);

    unbindRecvEvent();

    MySerialPort::sendAndRecv(serial, writtenData, recvData, recvLen);

    bindRecvEvent();

    if (widget) widget->setEnabled(true);
}

void MySerialWidget::asyncSend(QByteArray &writtenData, QWidget *widget)
{
    if (!serial || !serial->isOpen()) return;

    if (widget) widget->setEnabled(false);

    MySerialPort::send(serial, writtenData);

    if (widget)
        QTimer::singleShot(1000, this, [=]() {
            widget->setEnabled(true);
        });
}

void MySerialWidget::sendCmd(MyCommand &cmd, QWidget *button)
{
    if (cmd.mode == SERIAL_MODE_SYNCHRONOUS) {
        QByteArray responseData;
        int len = 0;

        syncSendAndRecv(cmd.data, responseData, len, button);
        if (len > 0) {
            qDebug() << "send and recv:" << responseData.length() << responseData.toHex(' ');
        }
    } else {
        asyncSend(cmd.data, button);
    }
}

void MySerialWidget::send(QByteArray &buf)
{
    MySerialPort::send(serial, buf);
}


void MySerialWidget::handleReadyRead()
{
    while (serial && serial->bytesAvailable()) {
        QByteArray newdata = serial->readAll();
        qDebug() << "<<=== recv" << newdata.length() << "bytes";
        qDebug() << newdata.toHex(' ');
        if (leftArray.length() == 0) {
            emit sign_dataReaded(newdata, leftArray);
        } else {
            QByteArray data ;
            data.append(leftArray);
            data.append(newdata);
            qDebug() << "<<=== proc" << data.length() << "bytes";
            qDebug() << data.toHex(' ');
            leftArray.clear();
            emit sign_dataReaded(data, leftArray);
        }        
    }
}

void MySerialWidget::handleBytesWritten(qint64 bytes)
{
    qDebug() << __func__ << "written " << bytes << " bytes";
}

void MySerialWidget::handleErrorOccurred(QSerialPort::SerialPortError error)
{
    MYDebug();
    qDebug() << error;
}

void MySerialWidget::slot_timer_listPort()
{
    if (!ui->comboPorts->isEnabled()) return;

    int portcount = ui->comboPorts->count();
    int newcount = availablePorts().count();

    if (portcount != newcount) {
        on_btnRefreshPorts_clicked();
    }
}

void MySerialWidget::slot_timer_closePort()
{
    if (ui->comboPorts->isEnabled()) return;
    if (!serial || !serial->isOpen()) return;

    QString portname = serial->portName();
    bool found = false;
    foreach (const QSerialPortInfo &info, QSerialPortInfo::availablePorts()) {
        if (info.portName().compare(portname)==0) {
            found = true;
            break;
        }
    }
    if (!found) {
        on_btnClosePort_clicked();
        on_btnRefreshPorts_clicked();
    }
}
